部落格同步刊登 [IT 鐵人賽] Component 魔術方法 Day 5
上一篇提及了 import
與 require
之後,我們現在把重心移到元件本身的一些屬性上面。本次內容將會圍繞在 :is
這一個元件屬性上面,我們來看看這個魔術方法到底可以做到哪些事情。
還有,可以做到什麼程度(燦笑)
:is
屬性這個屬性相當特別,他的目的是,將使用這個屬性的元件替換掉,替換成你所 指定 的 Vue 元件。換句話說,你只要把你的 Vue 元件引入後,甚至可以不需要在父層元件設定 components
,就可以使用。
<template>
<section>
<div :is="myComponent" />
</section>
</template>
<script>
import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'App',
data () {
return {
myComponent: HelloWorld
}
}
}
</script>
請注意,:is
後面必須要接受一個 Vue 元件實體,或者是一個非同步傳輸的 Vue 元件實體。不然他是無法動作的。
那麼,這件事情可以帶來的最大好處是什麼?
忘記寫
components
也沒關係(欸不對)!
由於它的彈性,所以我們可以做一些原本要放在樣板邏輯當中的事情,舉例來說:
<template>
<section>
<ul>
<li @click="switchTab(1)">Tab 1</li>
<li @click="switchTab(2)">Tab 2</li>
</ul>
<Tab1 class="tab tab-1" v-show="tab === 1" />
<Tab2 class="tab tab-2" v-show="tab === 2" />
</section>
</template>
<script>
import Tab1 from '@/components/tab1.vue'
import Tab2 from '@/components/tab2.vue'
export default {
name: 'App',
components: {
Tab1,
Tab2
},
data () {
return {
tab: 1
}
},
methods: {
switchTab(tab) {
this.tab = tab
}
}
}
</script>
這是一個常見的例子,我們有分頁標籤,然後每個分頁標籤讀取了各自的分頁內容進來。如果在這邊使用 :is
可以怎麼做呢?
<template>
<section>
<ul>
<li
v-for="tab in tabs"
:key="tab.id"
v-text="tab.name"
@click.prevent="switchTab(tab.id)"
></li>
</ul>
<div :is="activiteTab">
</section>
</template>
<script>
import Tab1 from '@/components/tab1.vue'
import Tab2 from '@/components/tab2.vue'
export default {
name: 'App',
data () {
return {
activiteTab: Tab1,
tabs: [
{
id: 1,
name: 'Tab 1',
context: Tab1
},
{
id: 2,
name: 'Tab 2',
context: Tab2
}
]
}
},
methods: {
switchTab(id) {
let index = this.tabs.findIndex(t => t.id === id)
if (index > -1) {
this.activiteTab = this.tabs[index].context
}
}
}
}
</script>
如果你有 10 個頁籤,你就不用寫十次 v-show
之類的動作。這樣的做法跟傳統使用 components
的做法,就差在於你載入的動作。不過,這個魔術方法還是有一點點不一樣,特別是在生命週期上面。
如果使用了 :is
這個方式,你必須特別留意一些元件實體上的差異:
$refs
如果使用 :is
的時候,對於 mounted
有些為差異:
import
再指定,你在 mounted
可以馬上取得該元件。mounted
必須等待 200ms
之後才能拿到。:is
的元件,整個元件會被銷毀再重建。:is
的子元件是不是一定會在 DOM 結構樹當中。<script>
const HelloWorld = () => ({
component: import('@/components/HelloWorld.vue')
})
export default {
name: 'App',
data () {
return {
myComponent: HelloWorld
}
},
created () {
console.log('created, $refs count:', Object.keys(this.$refs).length)
},
mounted () {
console.log('mounted, $refs count:', Object.keys(this.$refs).length)
setTimeout(() => {
console.log('mounted after 200ms, $refs count:', Object.keys(this.$refs).length)
}, 200)
}
}
</script>
為何是 200ms
?
我們在使用非同步載入元件時,他有一個固定的結構:
const HelloWorld = () => ({
// 你需要一個返回 Promise 的 Vue 元件實體
component: import('@/components/HelloWorld.vue'),
// 這裡可以指定在讀取時,使用什麼 Vue 元件來呈現
// 這個元件不可以是非同步載入
loading: LoadingComponent,
// 這裡可以指定在讀取錯誤時(包含超時),使用什麼 Vue 元件來呈現
// 這個元件不可以是非同步載入
error: ErrorComponent,
// 預設非同步元件延遲時間,這就是 200ms 由來(預設值)
delay: 200,
// 定義錯誤元件何時會顯示,預設是 Infinity
timeout: 3000
})
所以說,如果你的 delay
設定成 3000 的話,那麼,你的 mounted
就必須要等待 3 秒之後,才能從 $refs
當中拿到你的子元件實體。
這一招如果寫在第三方套件裡面,超糟糕的(好孩子不要學)。
其實你會發現,我大多都圍繞在非同步載入這件事情。實際上,由於自己的開發專案需要,所以我使用了非常多奇怪的方法。之後會慢慢的讓大家看看(燦笑)。我們繼續看 import
與 :is
之間能做些什麼事情。
上面有提到了,非同步載入元件需要一個 Promise
,不是鑽石恆久遠,一顆就破產的那種。所以,我們圍繞著 Promise
來看看。
上面這一顆 55 分,D Color 兩個 Excellent(等等離題了)
還記得 import
本身也是一種 Promise
嗎?如果忘記了請回去看 [IT 鐵人賽] Component 魔術方法 Day 4
所以如果我們需要一大堆動態樣式的時候,可以怎麼做?如果我們有很多 .vue
檔案,而且你可以確認那些檔案不會因為 突然 git push -f
或是 rm -rf /
被砍掉,那麼你可以利用 computed
這件事來幫忙。
<template>
<section>
<div :is="loadedComponent"></div>
</section>
</template>
<script>
export default {
name: 'App',
data () {
return {
activedComponent: 'Tab1'
}
},
computed: {
loadedComponent () {
return ((component => {
return () => ({
component: import('@/components/' + component + '.vue')
})
})(this.activedComponent)
}
}
}
</script>
實際上操作結果:
記得我剛剛說的,每次被 :is
載入的元件,都是被消滅然後重新建立。所以,你不管呼叫幾次元件,他的 created
都會被呼叫出來。也就是說,每次的生命週期都回重新走過一遍。
所以,當你在這些生命週期,甚至是你的元件當中,有將任何動作 綁定 到全域變數上面的,請記得把他取消,例如:
window.addEventListener('click', function () { ... }, false)
我們來直接實作一個例子,我們在元件的 created
監聽某個事件,
export default {
name: 'HelloWorld',
created () {
window.addEventListener('click', function () {
console.log('HelloWorld Component click!')
}, false)
}
}
然後我們的順序是這樣,
click
事件。我們先前提過了,他會將元件銷毀之後再次重建,所以你剛剛的 綁定 動作,就會被再次執行一次。
一次綁定一次爽,一直綁定一直爽。
所以,當你使用 :is
來操作元件時,無論你是否用了非同步載入,
請務必確認綁定的動作有確實被移除。
其實 :is
應用範圍多數是在切換不同元件,但是,單純拿來切換元件實在是太浪費了。搭配 import
看起來多美妙你說是不是。當然,這邊還是得小心 $refs
的坑。
其實你用 require
也能做到類似效果,我就不贅述了。